package cn.toolck.my.spring.factory;

import cn.toolck.my.spring.annotation.Autowired;
import cn.toolck.my.spring.annotation.Component;
import cn.toolck.my.spring.annotation.Transactional;
import cn.toolck.my.spring.transation.TransationManager;
import net.sf.cglib.proxy.Enhancer;
import org.omg.CORBA.OBJ_ADAPTER;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * @ClassName ProxyFactory
 * @Description ProxyFactory
 * @Author Toolck Rin
 * @Date 2021/2/8 18:49
 * @Version 1.0
 **/
@Component("proxyFactory")
public class ProxyFactory {
    public static final String JDK_PROXY = "JDK_PROXY";
    public static final String CGLIB_PROXY = "CGLIB_PROXY";


    @Autowired
    public TransationManager transationManager;

    public Object getProxyObject(Object source) {
        Class<?>[] interfaces = source.getClass().getInterfaces();
        if (interfaces.length == 0) {
            return getProxyObjectByCglib(source);
        }
        return getProxyObjectByJdk(source);
    }

    /**
     * 可用于指定代理的类型
     * @param source 被代理对象
     * @param type 代理的类型
     * @return 代理对象
     */
    public Object getProxyObject(Object source, String type) {
        switch (type) {
            case JDK_PROXY:
                return getProxyObjectByJdk(source);
            case CGLIB_PROXY:
                return getProxyObjectByCglib(source);
            default:
                throw new IllegalStateException("Unexpected value: " + type);
        }
    }

    private Object getProxyObjectByCglib(Object source) {
        Class<?> sourceClass = source.getClass();
        return Enhancer.create(sourceClass, new net.sf.cglib.proxy.InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.isAnnotationPresent(Transactional.class)) {
                    Transactional annotation = method.getAnnotation(Transactional.class);
                    transationManager.begin();
                    try {
                        Object invoke = method.invoke(source, args);
                        transationManager.commit();
                        return invoke;
                    } catch (Exception e) {
                        Class<? extends Throwable>[] classes = annotation.rollbackFor();
                        Class<? extends Exception> aClass = e.getClass();
                        if (Arrays.asList(classes).contains(aClass)) {
                            transationManager.rollback();
                        }
                        throw e;
                    }
                }
                return method.invoke(source, args);
            }
        });
    }

    /**
     * 通过Jdk动态代理获取相应的代理对象
     * @param source
     * @return
     */
    private Object getProxyObjectByJdk(Object source) {
        Class<?>[] interfaces = source.getClass().getInterfaces();
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // method传入的是接口中的method所以没有获取到注解信息，因此要获取到原对象中对应方法上的注解才可以使用相应的事务
                Method method1 = source.getClass().getMethod(method.getName(), method.getParameterTypes());
                if (method1.isAnnotationPresent(Transactional.class)) {
                    Transactional annotation = method1.getAnnotation(Transactional.class);
                    transationManager.begin();
                    try {
                        Object invoke = method.invoke(source, args);
                        transationManager.commit();
                        return invoke;
                    } catch (Exception e) {
                        Class<? extends Throwable>[] classes = annotation.rollbackFor();
                        Class<? extends Exception> aClass = e.getClass();
                        for (Class<? extends Throwable> aClass1 : classes) {
                            if (aClass1.isAssignableFrom(e.getClass())) {
                                transationManager.rollback();
                                break;
                            }
                        }
                        throw e;
                    }
                }
                return method.invoke(source, args);
            }
        });
    }

    public TransationManager getTransationManager() {
        return transationManager;
    }

    public void setTransationManager(TransationManager transationManager) {
        this.transationManager = transationManager;
    }
}
